using System;
using System.Reactive.Concurrency;
using System.Reactive.Linq;

namespace CompositionalAudio
{
    public class SignalGenerator : IStereoSource
    {
        public IObservable<double> ChannelLeft { get; private set; }
        public IObservable<double> ChannelRight { get; private set; }

        public Func<double, double, double, double, double> Waveform { get; set; }
        public double Amplitude { get; set; }
        public double Frequency { get; set; }
        public double SampleRate { get; set; }
        public double DCLevel { get; set; }
        public double Noise { get; set; }
        public bool SetDCLevel { get; set; }
        public bool SetNoiseState { get; set; }
        
        private bool EndNow = false;


        public SignalGenerator() { }
        public SignalGenerator(Func<double, double, double, double, double> WaveForm, double Amplitude, double Frequency, double SampleRate)
        {
            this.Waveform = WaveForm;
            this.Amplitude = Amplitude;
            this.Frequency = Frequency;
            this.SampleRate = SampleRate;

            //var back = Observable.Generate<double, double>(0,                                 //initial condition
            //                   sampleNo => !EndNow,                                         //when To stop
            //                   sampleNo => (sampleNo >= SampleRate) ? 0 : sampleNo + 1,     //increment sampleNo
            //                   sampleNo => GenerateSample(sampleNo),                        //Generate Sample
            //                   sampleNo => new TimeSpan((long)(100000000 / SampleRate)),
            //                   NewThreadScheduler.Default
            //                   )
            //                   .Publish()
            //                   .RefCount();

            var back = Observable.Interval(new TimeSpan(10),NewThreadScheduler.Default)
                                 .Select(i=>GenerateSample(i))
                                 .Publish()
                                 .RefCount();

            ChannelLeft = back;
            ChannelRight = back;
        }

        public void Stop()
        {
            EndNow = true;
        }

        public double GenerateSample(double sampleNo)
        {
            double value = Waveform(sampleNo, Frequency, SampleRate, Amplitude);

            if (SetDCLevel)
            {
                value += DCLevel;
            }
            if (SetNoiseState)
            {
                Random r = new Random();
                value += Noise * r.Next();
            }

            return value;
        }

        public void Dispose()
        { }

        #region Static Waveform Methods
        public static Func<double, double, double, double, double> Sine = (sampleNoIn, Freq, SampleRate, Ampl) =>
        {
            double theta = 2.0 * Math.PI * Freq / SampleRate;
            return (Ampl * Math.Sin(sampleNoIn * theta));
        };

        public static Func<double, double, double, double, double> Cosine = (sampleNoIn, Freq, SampleRate, Ampl) =>
        {
            double theta = 2.0 * Math.PI * Freq / SampleRate;
            return (Ampl * Math.Cos(sampleNoIn * theta));
        };

        public static Func<double, double, double, double, double> Square = (sampleNoIn, Freq, SampleRate, Ampl) =>
        {
            double p = 2.0 * Freq / SampleRate;
            return (Math.Round(sampleNoIn * p) % 2.0 == 0.0 ? Ampl : -Ampl);
        };

        public static Func<double, double, double, double, double> Triangular = (sampleNoIn, Freq, SampleRate, Ampl) =>
        {
            double p = 2.0 * Freq / SampleRate;
            int ip = (int)Math.Round(sampleNoIn * p);
            return( 2.0 * Ampl * (1.0 - 2.0 * (ip % 2.0)) * (sampleNoIn * p - ip));
        };

        public static Func<double, double, double, double, double> Sawtooth = (sampleNoIn, Freq, SampleRate, Ampl) =>
        {
            double q = sampleNoIn * Freq / SampleRate;
            return (2.0 * Ampl * (q - Math.Round(q)));
        };
        #endregion
    }
}
